使用 Rust 实现 JS 原型链

在 JavaScript 中,继承的逻辑是通过原型链来实现的,JavaScript 对象有一个指向一个原型对象的链。当访问一个对象的属性时,会在当前对象以及对象的原型上层层向上进行搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

比如,在JS中实例化一个数组对象时,它会继承 Array.prototype 这个原型对象的所有属性和方法:

let arr = new Array();
a.push == Array.prototype.push; // true

那么,当我们要实现一个 JS 解释引擎时,其中很关键的部分就是要实现原型对象和原型链。

内置对象

在 JavaScript 中,几乎所有的对象都是 Object 类型的实例,它们都会从 Object.prototype 继承属性和方法,所以我们先定义一个 Object 的结构。

// 对象
pub struct Object {
    // 静态属性和方法,比如 Object.keys
    pub property: HashMap,
    // 属性列表,对象的属性列表需要次序
    pub property_list: Vec,
    // 内置属性
    pub inner_property: HashMap,
    // 原型对象,用于查找原型链
    // 如果是构造方法对象,如 Object,则指向一个真实存在的 Object
    // 如:Array.prototype[key] = value
    // 如:Array.prototype.constructor = Array
    pub prototype: Option>>,
    // 如果是实例,则存在 constructor 值,指向构造方法
    // 如: arr.constructor = Array
    pub constructor: Option>>,
    // 对象的值
    value: Option>,
}

由于在 JS 中,内置的对象 Array 和 Function 等,也都是一个对象,所以我们来声明一个方法,获取一个最原始的对象,作为这些内置对象的基本对象:

pub fn new_global_object() -> Rc> {
  let object = Rc::new(RefCell::new(Object::new(None)));
  let object_clone = Rc::clone(&object);
  let mut object_mut = (*object_clone).borrow_mut();
  // 创建原型链
  let prototype =  Rc::new(RefCell::new(Object::new(None)));
  // 创建 constructor 弱引用,指向自己
  // 即 Object.prototype.constructor == Object
  (*prototype).borrow_mut().define_property(String::from("constructor"), Property {
    enumerable: false,
    value: Value::RefObject(Rc::downgrade(&object)),
  });
 // 绑定原型对象到 Object 上
  object_mut.prototype = Some(prototype);
  object
}

基于上面这个方法,我们创建几个内置对象,如 Array、Function 等:

// Object
let object = new_global_object();
// Array
let array = new_global_object();
// Function
let function = new_global_object();

// 全局对象
pub struct Global {
  pub object: Rc>,
  pub array: Rc>,
  pub function: Rc>,
}
let global = Global{
  object,
  array,
  function,
};

绑定原型方法

这个时候,我们只是获取了几个对象的声明,但是还没有绑定这个内置对象的原型方法,所以需要对 Object、Array 等来绑定原型方法。

首先,我们需要定义原型方法的结构,原型方法需要接受一个执行的上下文,其中需要包含一些内置对象(Object、Array )的定义,便于在原型方法内创建新的对象实例,另外需要包含当前执行的 this,用于对 this 进行修改。

// 内置方法的结构
pub type BuiltinFunction = fn(&mut CallContext, Vec) -> Value;
// 执行上下文
pub struct CallContext<'a> {
  pub global: &'a Global,
  pub this: Weak>
}

下之后,我们就可以声明一些原型方法,下面以 Array 为例,绑定 Array.prototype.toString、push 和 join 等方法。

// 绑定 Array 的原型方法
pub fn bind_global_array(global: &Global) {
  let arr = (*global.array).borrow_mut();
  if let Some(prop)= &arr.prototype {
    let prototype_rc = Rc::clone(prop);
    let mut prototype = prototype_rc.borrow_mut();
    // 绑定 Array.prototype.toString
    let name = String::from("toString");
    prototype.define_property(name.clone(), Property { enumerable: true, value: builtin_function(global, name, 0f64, array_to_string) });
      
    // 绑定 Array.prototype.join
    let name = String::from("join");
    prototype.define_property(name.clone(), Property { enumerable: true, value: builtin_function(global, name, 1f64, array_join) });
      
    // 绑定 Array.prototype.push
    let name = String::from("push");
    prototype.define_property(name.clone(), Property { enumerable: true, value: builtin_function(global, name, 1f64, array_push) });
  }
}

// toString 方法,实际上相当于 array.join()
fn array_to_string(ctx: &mut CallContext, _: Vec) -> Value {
  array_join(ctx, vec![])
}

// join 方法,接受一个参数,作为 join 的连接符
fn array_join(ctx: &mut CallContext, args: Vec) -> Value {
  let mut join = ",";
  if args.len() > 0 {
    if let Value::String(join_param) = &args[0] {
      join = join_param;
    }
  }
  let mut string_list: Vec = vec![];
  // 创建迭代数组时的闭包
  let iter = |_: i32, value: &Value| {
    string_list.push(value.to_string());
  };
  // 迭代数组
  array_iter_mut(ctx, iter);
  Value::String(string_list.join(join))
}

// 接收一个 闭包,用来迭代数组
fn array_iter_mut(ctx: &mut CallContext, mut callback: F) {
  let this_origin = ctx.this.upgrade();
  let this_rc = this_origin.unwrap();
  let this = this_rc.borrow_mut();
  let len = this.get_property_value(String::from("length"));
  if let Value::Number(len) = len {
    for index in 0..(len as i32) {
      (callback)(index, &this.get_property_value(index.to_string()));
    }
  }
}

// Array.prototype.push,可以一次 push 多个值
fn array_push(ctx: &mut CallContext, args: Vec) -> Value {
  // 插入值
  let this_rc = ctx.this.upgrade().unwrap();
  let mut this = this_rc.borrow_mut();
  // 获取长度
  let mut len = this.get_property_value(String::from("length")).to_number().unwrap() as usize;
  for value in args.iter() {
    // 向 Object 中定义的 property 这个 HashMap 中添加值
    this.define_property(len.to_string(), Property { enumerable: true, value: value.clone() });
    len += 1
  }
  // 计算push 后的长度
  let new_length = Value::Number(len as f64);
  this.define_property(String::from("length"),  Property { enumerable: false, value: new_length.clone() });
  return new_length
}

创建实例

当我们有了全局内置对象和各种原型方法后,我们就可以创建对应的实例了,例如创建一个 array 实例:

 pub fn create_array(global: &Global, length: usize) -> Value {
  // 由于 js 中所有的对象都是 object
  let array = Rc::new(RefCell::new(Object::new(value)));
  let array_clone = Rc::clone(&array);
  let mut array_mut = (*array_clone).borrow_mut();
  // 绑定 array.constructor = global.Array
  // 此时就相当于 array 有了 Array 上的各种原型方法
  array_mut.constructor = Some(Rc::clone(&global.array));
  object
  // 定义 array 的长度
  array_mut.define_property(String::from("length"),  Property {
     enumerable: true,
     value: Value::Number(length as f64),
   });
  Value::Array(array)
}

执行效果

#[test]
fn run_object_keys() {
  let mut jsi = JSI::new();
  let result = jsi.run(String::from("\
  let obj = { a: 123, b: false, c: 'xxx'}\n
  Object.keys(obj).toString()"));
  assert_eq!(result , Value::String(String::from("a,b,c")));
}